<?php
namespace core\models;
/**
 * 加密、解码
 * Class Password_Cursor
 * @author  Howard
 * @package
 */
class Password_Cursor {
    /**
     * 加密
     * @param string $plaintext 要加密码的字符串（必须是utf-8字符）
     * @param int    $expire    有效期（秒），0为不过期
     * @param string $key       钥匙。如：abcd123pk。若设置了钥匙则在解码时需要用到此钥匙来解密，请妥善保存钥匙！
     * @return string 返回加密后的字符串
     * @throws Exception
     * @author ZhouHaoHua <53027327@qq.com>
     * @date   date
     */
    public function cursorEncode( string $plaintext, int $expire = 0, string $key = '' ) {
        // 1. 先生成明文$plaintext的md5假设：c4ca4238a0b923820dcc509a6f75849b
        // 2. md5字符倒转过来，并打乱它。
        // 3. 把明文转码成纯数字。
        // 4. 纯数字的空格用空格左边数字的字母表示形式替代。
        $cipherBefore = '';
        $cipherText = '';
        if( $plaintext != '' ) {
            $plaintext .= 'exp:' . ( $expire > 0 ? time() + $expire : 0 );

            //$plaintext = mb_convert_encoding($plaintext, 'HTML-ENTITIES', $charset);
            $md5 = md5( !empty( $key ) ? $key : $plaintext );
            $md5_strrev = strrev( $md5 );
            $a0_9z = $this->number_to_letter();
            $cipherBefore = $md5_strrev;
            //$cipherBefore = $md5_strrev . strrev(str_replace(['.',' '],['0','1'],microtime())); // 字符串补充后长度=32
            //$cipherBefore = substr(str_shuffle( $cipherBefore ), 0, 32) ;
            $ascii = base64_encode( $plaintext );
            $ascii = \Z::Strings()->stringToAscii( $ascii, ',' );
            // 把数字换成字母
            $cipherText = $this->numbers_letters( $ascii, $a0_9z, ',' );
            //将数字的钥匙字母打入到md5中
            $_tmp = str_split( $a0_9z );
            $_tmp1 = str_split( $cipherBefore );
            for( $i = 0; $i < 10; $i++ ) {
                $_tmp1[ $i + $i ] = $_tmp[ $i ];
            }
            $cipherBefore = implode( '', $_tmp1 );
        }
        $output = $cipherBefore . $cipherText;
        // $output = gzcompress($output);
        return $output;
    }

    /**
     * 解码
     * @param string $cipher_text 已加密的字符串
     * @param string $key         字符编码。上次生成此密码串时填写的钥匙串！
     * @return bool|string 返回解码后的字符。utf-8
     * @author ZhouHaoHua <53027327@qq.com>
     */
    public function cursorDecode( string $cipher_text, $key = '' ) {
        $plaintext = '';
        if( strlen( $cipher_text ) > 32 ) {
            //$cipher_text = gzuncompress($cipher_text);
            $_key_md = substr( $cipher_text, 0, 32 );
            $_key_array = str_split( $_key_md );
            // 取钥匙
            $keys = [ ];
            foreach( $_key_array as $k => $v ) {
                $keys[] = $_key_array[ $k + $k ];
                if( sizeof( $keys ) >= 10 ) {
                    break;
                }
            }
            $zkeys = array_flip( $keys );
            // 用钥匙去解码（字母变数字）
            $letters_to_numbers = '';
            $letters = str_split( substr( $cipher_text, 32 ) );
            foreach( $letters as $k => $letter ) {
                if( is_numeric( $letter ) ) {
                    $letters_to_numbers .= ' ';
                } else {
                    $letters_to_numbers .= $zkeys[ $letter ];
                }
            }
            $plaintext = \Z::Strings()->asciiToString( $letters_to_numbers );
            $plaintext = base64_decode( $plaintext );
            //$plaintext = mb_convert_encoding($plaintext, $charset, 'HTML-ENTITIES');
            // 检查是否已过期
            $matches = [ ];
            if( $plaintext != '' ) {
                $md5_key_or_plain = strrev( md5( ( !empty( $key ) ? $key : $plaintext ) ) );
                $md5_key_or_plain = str_split( $md5_key_or_plain );
                foreach( $keys as $k => $v ) {
                    $md5_key_or_plain[ $k + $k ] = $v;
                }
                $md5_key_or_plain_merge = implode( '', $md5_key_or_plain );
                if( $md5_key_or_plain_merge !== substr( $cipher_text, 0, 32 ) ) {  // 对码不匹配也不给输出字符
                    $plaintext = '';
                }
            }
            if( !preg_match( '/exp:\d+$/', $plaintext, $matches ) ) {
                $plaintext = '';
            } else {
                $array = explode( ':', $matches[0] );
                if( !is_numeric( $array[1] ) ) {
                    $plaintext = '';
                }
                if( time() > $array[1] && (int)$array[1] !== 0 ) {   // 过期了
                    $plaintext = '';
                }
                if( $plaintext != '' ) {
                    $plaintext = substr( $plaintext, 0, ( strlen( $plaintext ) - strlen( $matches[0] ) ) );
                }
            }
        }
        return $plaintext;
    }

    /**
     * 0-9数字转字母（打乱方式）
     * @param string $filter 要过滤哪些字母。去重后字符长度不能大于15个
     * @return string 返回0-9的字母表示方式。如：vojmxyihns，代表v=0,o=1,j=2...s=9。每次执行生成的结果都不一样！
     * @throws Exception
     * @internal param int $str
     * @author   ZhouHaoHua <53027327@qq.com>
     */
    private function number_to_letter( string $filter = '' ) {
        $leters = range( 'a', 'z' );
        $filters = str_split( $filter );
        $filters = array_unique( $filters );
        if( sizeof( $filters ) > 15 ) {
            throw new \Exception( '$filter去重后字符长度不能大于15' );
        }
        $use_array = explode( ',', implode( ',', ( array_diff( $leters, $filters ) ) ) );
        // 计算出0-9的各个代表字符
        $array = [ ];
        for( $i = 0; $i < 10; $i++ ) {
            $array[ $i ] = $this->make_only( $array, $use_array );
        }
        $key = implode( '', $array );
        return $key;
    }

    /**
     * 用$use_array中的字母随机生成与$array中不存在的字母
     * @param array $array
     * @param array $use_array
     * @return string
     * @author ZhouHaoHua <53027327@qq.com>
     */
    private function make_only( array $array, array $use_array ) {
        $rand = mt_rand( ord( $use_array[0] ), ord( $use_array[ sizeof( $use_array ) - 1 ] ) );
        $let = chr( $rand );
        if( !in_array( $let, $array ) ) {
            return $let;
        } else {
            return $this->make_only( $array, $use_array );
        }
    }

    /**
     * 把数字串变成字母串（生成字符串后，串中的空格用数字替代）
     * @param string $numbers   数字字符串。如：12 5689 8 474
     * @param string $key       钥匙。10位
     * @param string $delimiter 分隔符
     * @return string 返回转成字母后的字符串
     * @throws Exception
     * @author ZhouHaoHua <53027327@qq.com>
     */
    private function numbers_letters( string $numbers, string $key, string $delimiter = ' ' ) {
        $letters = '';
        $ks = str_split( $key );
        if( sizeof( $ks ) != 10 ) {
            throw new \Exception( '$key必须是10位不重复的字母串' );
        }
        $ns = str_split( $numbers );
        foreach( $ns as $v ) {
            if( $v === $delimiter ) {
                $letters .= mt_rand( 0, 9 );
            } else {
                $letters .= $ks[ $v ];
            }
        }
        return $letters;
    }
}